package com.itcast.activiti.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.itcast.activiti.cmd.GetProcessCmd;
import com.itcast.activiti.cmd.GetProcessDefinitionCacheEntryCmd;
import com.itcast.activiti.constant.NodeKeyTag;
import com.itcast.activiti.domain.StartProcessInstance;
import com.itcast.activiti.domain.TaskStart;
import com.itcast.activiti.entity.ApproveAddSign;
import com.itcast.activiti.service.*;
import com.itcast.activiti.utils.ActivitiPage;
import com.itcast.activiti.utils.TimeUtils;
import com.itcast.activiti.utils.VariablesUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.history.*;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.task.TaskInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class IHistoryServiceImpl implements IHistoryService {

    @Autowired
    private HistoryService historyService;
    @Autowired
    private IProcessRuntimeService iProcessRuntimeService;
    @Autowired
    private ITaskRuntimeService iTaskRuntimeService;
    @Autowired
    private IDeployRepositoryService iDeployRepositoryService;
    @Autowired
    private IActIdUserService iActIdUserService;
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private IApproveAddSignService iApproveAddSignService;

    @Override
    public ActivitiPage listHistoricProcessInstancePage(ActivitiPage activitiPage) {
        JSONObject condition = activitiPage.getCondition();
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc();
        String businessKey = condition.getString(StartProcessInstance.Fields.businessKey);
        String tenantId = condition.getString(StartProcessInstance.Fields.tenantId);
        if (StringUtils.hasText(businessKey)) {
            historicProcessInstanceQuery.processInstanceBusinessKey(businessKey);
        }
        if (StringUtils.hasText(tenantId)) {
            historicProcessInstanceQuery.processInstanceTenantId(tenantId);
        }
        Integer startIndex = Math.toIntExact(activitiPage.getCurrent());
        Integer maxItems = Math.toIntExact(activitiPage.getSize());
        List<HistoricProcessInstance> historicProcessInstanceList = historicProcessInstanceQuery.listPage(startIndex, maxItems);
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (HistoricProcessInstance historicActivityInstance : historicProcessInstanceList) {
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("id", historicActivityInstance.getId());
            resultMap.put("superProcessInstanceId", historicActivityInstance.getSuperProcessInstanceId());
            resultMap.put("startTime", TimeUtils.format(historicActivityInstance.getStartTime()));
            resultMap.put("endTime", TimeUtils.format(historicActivityInstance.getEndTime()));
            resultMap.put("deploymentId", historicActivityInstance.getDeploymentId());
            resultMap.put("businessKey", historicActivityInstance.getBusinessKey());
            resultMap.put("startUserId", historicActivityInstance.getStartUserId());
            resultMap.put("startActivityId", historicActivityInstance.getStartActivityId());
            resultMap.put("name", historicActivityInstance.getName());
            resultMap.put("durationInMillis", historicActivityInstance.getDurationInMillis());
            resultMap.put("tenantId", historicActivityInstance.getTenantId());
            resultMap.put("deleteReason", historicActivityInstance.getDeleteReason());
            resultMap.put("description", historicActivityInstance.getDescription());
            resultMap.put("processDefinitionId", historicActivityInstance.getProcessDefinitionId());
            resultMap.put("processDefinitionKey", historicActivityInstance.getProcessDefinitionKey());
            resultMap.put("processDefinitionName", historicActivityInstance.getProcessDefinitionName());
            resultMap.put("processDefinitionVersion", historicActivityInstance.getProcessDefinitionVersion());
            resultList.add(resultMap);
        }
        activitiPage.setTotal(historicProcessInstanceQuery.count());
        activitiPage.setRecords(resultList);
        return activitiPage;
    }


    @Override
    public List<Map<String, Object>> listHistoricActivityInstanceByBusinessKey(String businessKey, String tenantId) {
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(historicProcessInstance, businessKey + " is not historic process instance");
        String processInstanceId = historicProcessInstance.getId();
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                                                                                    .processInstanceId(processInstanceId)
                                                                                    .activityTenantId(tenantId)
                                                                                    // 查询已经完成的任务
                                                                                    .orderByHistoricActivityInstanceStartTime().asc()
                                                                                    .list();
        if (historicActivityInstanceList.isEmpty()) {
            return null;
        }
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            String startTime = TimeUtils.format(historicActivityInstance.getStartTime());
            String taskId = historicActivityInstance.getTaskId();
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("id", historicActivityInstance.getId());
            resultMap.put("businessKey", businessKey);
            resultMap.put("processDefinitionId", historicActivityInstance.getProcessDefinitionId());
            resultMap.put("processInstanceId", historicActivityInstance.getProcessInstanceId());
            resultMap.put("startTime", startTime);
            resultMap.put("endTime", TimeUtils.format(historicActivityInstance.getEndTime()));
            resultMap.put("activityName", historicActivityInstance.getActivityName());
            resultMap.put("activityId", historicActivityInstance.getActivityId());
            if ("userTask".equals(historicActivityInstance.getActivityType())) {
                Map<String, Object> assignee = getAssignee(historicActivityInstance.getAssignee(), historicActivityInstance.getExecutionId(), processInstanceId, historicActivityInstance.getProcessDefinitionId());
                resultMap.putAll(assignee);
            }
            resultMap.put("activityType", historicActivityInstance.getActivityType());
            resultMap.put("durationInMillis", historicActivityInstance.getDurationInMillis());
            resultMap.put("tenantId", historicActivityInstance.getTenantId());
            resultMap.put("time", TimeUtils.format(historicActivityInstance.getTime()));
            resultMap.put("taskId", taskId);
            resultMap.put("comment", iTaskRuntimeService.listComment(historicActivityInstance.getTaskId()));
            resultMap.put("audit", historicActivityInstance.getEndTime() != null ? this.getTaskAuditType(taskId) : false);
            String endTime = TimeUtils.format(historicActivityInstance.getEndTime() != null ? historicActivityInstance.getEndTime() : new Date());
            resultMap.put("duration", TimeUtils.dateDiff(startTime, endTime, null));
            resultMap.put("taskAttachments", iTaskRuntimeService.listTaskAttachments(taskId));
            resultList.add(resultMap);
        }
        return resultList;
    }

    @Override
    public Map<String, Object> listHistoricActivityInstanceByBusinessKeyDesc(String businessKey, String tenantId) {
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(historicProcessInstance, businessKey + " is not historic process instance");
        String processInstanceId = historicProcessInstance.getId();
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                                                                                    .processInstanceId(processInstanceId)
                                                                                    .orderByHistoricActivityInstanceStartTime().desc()
                                                                                    .list();
        if (historicActivityInstanceList.isEmpty()) {
            return null;
        }
        HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(0);
        String taskId = historicActivityInstance.getTaskId();
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("businessKey", businessKey);
        resultMap.put("processDefinitionId", historicActivityInstance.getProcessDefinitionId());
        resultMap.put("processInstanceId", historicActivityInstance.getProcessInstanceId());
        resultMap.put("activityName", historicActivityInstance.getActivityName());
        resultMap.put("activityId", historicActivityInstance.getActivityId());
        if ("userTask".equals(historicActivityInstance.getActivityType())) {
            Map<String, Object> assignee = getAssignee(historicActivityInstance.getAssignee(), historicActivityInstance.getExecutionId(), processInstanceId, historicActivityInstance.getProcessDefinitionId());
            resultMap.putAll(assignee);
        }
        resultMap.put("taskId", taskId);
        resultMap.put("audit", historicActivityInstance.getEndTime() != null ? this.getTaskAuditType(taskId) : false);
        return resultMap;
    }

    @Override
    public ActivitiPage listHistoricTaskInstancePage(ActivitiPage activitiPage) {
        JSONObject condition = activitiPage.getCondition();
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                                                                            .orderByHistoricTaskInstanceStartTime().asc();
        if (StringUtils.hasText(condition.getString(StartProcessInstance.Fields.businessKey))) {
            historicTaskInstanceQuery.processInstanceBusinessKey(condition.getString(StartProcessInstance.Fields.businessKey));
        }
        if (StringUtils.hasText(condition.getString(TaskStart.Fields.assignee))) {
            historicTaskInstanceQuery.taskAssignee(condition.getString(TaskStart.Fields.assignee));
        }
        if (StringUtils.hasText(condition.getString(TaskStart.Fields.tenantId))) {
            historicTaskInstanceQuery.taskTenantId(condition.getString(TaskStart.Fields.tenantId));
        }
        Integer startIndex = Math.toIntExact(activitiPage.getCurrent());
        Integer maxItems = Math.toIntExact(activitiPage.getSize());
        List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery.listPage(startIndex, maxItems);
        List<HistoricTaskInstance> historicTaskInstances = historicTaskInstanceList.stream().sorted((t1, t2) -> {
            if (t1.getEndTime() != null && t2.getEndTime() != null) {
                return t1.getEndTime().compareTo(t2.getEndTime());
            }
            if (t1.getEndTime() == null && t2.getEndTime() != null) {
                return new Date().compareTo(t2.getEndTime());
            }
            if (t1.getEndTime() != null && t2.getEndTime() == null) {
                return t1.getEndTime().compareTo(new Date());
            }
            return 1;
        }).collect(Collectors.toList());
        List<Map<String, Object>> resultList = new ArrayList<>();
        String processDefinitionId = historicTaskInstances.get(0).getProcessDefinitionId();
        Map<String, Object> lastMap = null;
        ManagementService managementService = processEngine.getManagementService();
        managementService.executeCommand(new GetProcessCmd(processDefinitionId));
        ProcessDefinitionCacheEntry processDefinitionCacheEntry = managementService.executeCommand(new GetProcessDefinitionCacheEntryCmd(processDefinitionId, processEngine));
        Process process = processDefinitionCacheEntry.getProcess();
        for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
            if (historicTaskInstance.getDeleteReason() != null) {
                continue;
            }
            String taskId = historicTaskInstance.getId();
            String processInstanceId = historicTaskInstance.getProcessInstanceId();
            String startTime = TimeUtils.format(historicTaskInstance.getStartTime());
            Map<String, Object> resultMap = new HashMap<>();
            UserTask userTask = (UserTask) process.getFlowElement(historicTaskInstance.getTaskDefinitionKey());
            resultMap.put("id", taskId);
            resultMap.put("processInstanceId", processInstanceId);
            resultMap.put("processDefinitionId", historicTaskInstance.getProcessDefinitionId());
            resultMap.put("startTime", startTime);
            resultMap.put("endTime", TimeUtils.format(historicTaskInstance.getEndTime()));
            resultMap.put("name", historicTaskInstance.getName());
            resultMap.put("executionId", historicTaskInstance.getExecutionId());
            Map<String, Object> assignee = getAssignee(historicTaskInstance.getAssignee(), historicTaskInstance.getExecutionId(), processInstanceId, historicTaskInstance.getProcessDefinitionId());
            resultMap.putAll(assignee);
            resultMap.put("taskDefinitionKey", historicTaskInstance.getTaskDefinitionKey());
            resultMap.put("priority", historicTaskInstance.getPriority());
            resultMap.put("tenantId", historicTaskInstance.getTenantId());
            resultMap.put("time", TimeUtils.format(historicTaskInstance.getTime()));
            resultMap.put("claimTime", TimeUtils.format(historicTaskInstance.getClaimTime()));
            resultMap.put("comment", iTaskRuntimeService.listComment(taskId));
            resultMap.put("audit", historicTaskInstance.getEndTime() != null ? this.getTaskAuditType(taskId) : false);
            resultMap.put("description", historicTaskInstance.getDescription());
            String endTime = TimeUtils.format(historicTaskInstance.getEndTime() != null ? historicTaskInstance.getEndTime() : new Date());
            resultMap.put("duration", TimeUtils.dateDiff(startTime, endTime, null));
            resultMap.put("taskAttachments", iTaskRuntimeService.listTaskAttachments(taskId));
            resultMap.put("multiApproveMode", userTask.hasMultiInstanceLoopCharacteristics() ? 1 : 2);
            if (lastMap != null) {
                lastMap.put("nextEquals", lastMap.get("taskDefinitionKey").equals(historicTaskInstance.getTaskDefinitionKey()));
            }
            lastMap = resultMap;
            resultList.add(resultMap);
        }
        activitiPage.setTotal(historicTaskInstanceQuery.count());
        activitiPage.setRecords(resultList);
        return activitiPage;
    }

    @Override
    public HistoricProcessInstance getHistoricProcessInstanceByBusinessKey(String businessKey, String tenantId) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        historicProcessInstanceQuery.processInstanceBusinessKey(businessKey);
        if (StringUtils.hasText(tenantId)) {
            historicProcessInstanceQuery.processInstanceTenantId(tenantId);
        }
        HistoricProcessInstance historicProcessInstance = historicProcessInstanceQuery.singleResult();
        return historicProcessInstance;
    }

    @Override
    public List<HistoricTaskInstance> getHistoryTaskListByProcessInstanceId(String processInstanceId, String sort) {
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
        historicTaskInstanceQuery.processInstanceId(processInstanceId)
                                 .orderByTaskCreateTime();
        if (Sort.Direction.DESC.name().equals(sort)) {
            historicTaskInstanceQuery.desc();
        } else if (Sort.Direction.ASC.name().equals(sort)) {
            historicTaskInstanceQuery.asc();
        }
        return historicTaskInstanceQuery.list();
    }

    @Override
    public List<HistoricActivityInstance> getHistoricActivityInstanceByExecutionId(String executionId) {
        return historyService.createHistoricActivityInstanceQuery()
                             .executionId(executionId)
                             .finished()
                             .list();
    }

    @Override
    public List<HistoricVariableInstance> getHistoricVariableByTaskId(String taskId, String variableName) {
        if (!StringUtils.hasText(taskId)) {
            return null;
        }
        HistoricVariableInstanceQuery historicVariableInstanceQuery = historyService.createHistoricVariableInstanceQuery();
        historicVariableInstanceQuery.taskId(taskId);
        if (StringUtils.hasText(variableName)) {
            historicVariableInstanceQuery.variableName(variableName);
        }
        return historicVariableInstanceQuery.list();
    }

    @Override
    public List<HistoricVariableInstance> getHistoricVariableByProcessInstanceId(String processInstanceId, String variableName) {
        if (!StringUtils.hasText(processInstanceId)) {
            return null;
        }
        HistoricVariableInstanceQuery historicVariableInstanceQuery = historyService.createHistoricVariableInstanceQuery();
        historicVariableInstanceQuery.processInstanceId(processInstanceId);
        if (StringUtils.hasText(variableName)) {
            historicVariableInstanceQuery.variableName(variableName);
        }
        return historicVariableInstanceQuery.list();
    }

    @Override
    public boolean getTaskAuditType(String taskId) {
        List<HistoricVariableInstance> historicVariableInstance = getHistoricVariableByTaskId(taskId, TaskStart.Fields.audit);
        if (historicVariableInstance == null || historicVariableInstance.size() == 0) {
            return true;
        }
        return Boolean.valueOf(historicVariableInstance.get(0).getValue().toString());
    }

    @Override
    public Set<String> getHistoricAssignee(String businessKey, String tenantId) {
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().processInstanceBusinessKey(businessKey).taskTenantId(tenantId).list();
        return list.stream()
                   .filter(historicTaskInstance -> Objects.nonNull(historicTaskInstance.getEndTime()))
                   .map(TaskInfo::getAssignee)
                   .filter(assignee -> !StringUtils.isEmpty(assignee))
                   .collect(Collectors.toSet());
    }

    @Override
    public Map<String, Object> getNextTask(String businessKey, String tenantId, String taskId) {
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(historicProcessInstance, businessKey + " is not historic process instance");
        String processInstanceId = historicProcessInstance.getId();
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                                                                                    .processInstanceId(processInstanceId)
                                                                                    .orderByHistoricActivityInstanceStartTime().desc()
                                                                                    .list();
        if (historicActivityInstanceList.isEmpty()) {
            return null;
        }
        HistoricActivityInstance lastAppHistoricActivityInstance = null;
        if (taskId != null) {
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
                if (taskId.equals(historicActivityInstance.getTaskId())) {
                    lastAppHistoricActivityInstance = historicActivityInstance;
                    break;
                }
            }
        }

        HistoricActivityInstance lastHistoricActivityInstance = historicActivityInstanceList.get(0);
        if (lastAppHistoricActivityInstance != null && lastHistoricActivityInstance.getActivityId().equals(lastAppHistoricActivityInstance.getActivityId())) {
            return null;
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("businessKey", businessKey);
        resultMap.put("processDefinitionId", lastHistoricActivityInstance.getProcessDefinitionId());
        resultMap.put("processInstanceId", lastHistoricActivityInstance.getProcessInstanceId());
        resultMap.put("activityName", lastHistoricActivityInstance.getActivityName());
        resultMap.put("activityId", lastHistoricActivityInstance.getActivityId());
        resultMap.put("taskId", taskId);
        resultMap.put("audit", lastHistoricActivityInstance.getEndTime() != null && this.getTaskAuditType(taskId));
        String replace = lastHistoricActivityInstance.getActivityId().replace(NodeKeyTag.USERTASK_PREFIX, "");
        ApproveAddSign addSign = iApproveAddSignService.findById(replace);
        resultMap.put("addSign", addSign);
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            if (lastHistoricActivityInstance.getActivityId().equals(historicActivityInstance.getActivityId())) {
                Map<String, Object> assigneeMap = getAssignee(historicActivityInstance.getAssignee(), historicActivityInstance.getExecutionId(), processInstanceId, historicActivityInstance.getProcessDefinitionId());
                String hisEmail = resultMap.getOrDefault("email", "").toString();
                String hisAssignee = resultMap.getOrDefault("assignee", "").toString();
                if (StringUtils.isEmpty(hisEmail)) {
                    resultMap.putAll(assigneeMap);
                } else {
                    String email = hisEmail + "," + assigneeMap.get("email").toString();
                    String assignee = hisAssignee + "," + assigneeMap.get("assignee").toString();
                    resultMap.put("email", email);
                    resultMap.put("assignee", assignee);
                }
            } else {
                break;
            }
        }
        return resultMap;
    }

    private Map<String, Object> getAssignee(String assignee, String executionId, String processInstanceId, String processDefinitionId) {
        Map<String, Object> map = new HashMap<>();
        map.put("email", assignee);
        map.put("assignee", assignee);
        if (StringUtils.hasText(assignee)) {
            String userName = iActIdUserService.getUserNameByemail(assignee);
            if (StringUtils.hasText(userName)) {
                map.put("assignee", userName);
                return map;
            }
            return map;
        }
        Execution execution = iProcessRuntimeService.getExecution(executionId);
        if (execution == null) {
            return map;
        }
        String curActivityId = execution.getActivityId();
        BpmnModel bpmnModel = iDeployRepositoryService.getBpmnModelByProcessDefinitionId(processDefinitionId);
        FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(curActivityId);
        List<String> candidateUsers = ((UserTask) curFlowNode).getCandidateUsers();
        if (candidateUsers == null || candidateUsers.size() == 0) {
            return map;
        }
        // 获选用户
        String candidateUser = VariablesUtil.regExp(candidateUsers.get(0));
        List<HistoricVariableInstance> historicVariableInstance = getHistoricVariableByProcessInstanceId(processInstanceId, candidateUser);
        if (historicVariableInstance != null && historicVariableInstance.size() != 0) {
            HistoricVariableInstance historicVariableInstance1 = historicVariableInstance.get(0);
            JSONArray emails = JSON.parseArray(historicVariableInstance1.getValue().toString());
            Set<String> emailes = new TreeSet<>();
            Set<String> userNames = new TreeSet<>();
            emails.forEach(value -> {
                emailes.add(value.toString());
                String userName = iActIdUserService.getUserNameByemail(value.toString());
                if (StringUtils.hasText(userName)) {
                    userNames.add(userName);
                } else {
                    String userNameById = iActIdUserService.getUserNameById(value.toString());
                    userNames.add(StringUtils.hasText(userNameById) ? userNameById : VariablesUtil.regExp(value.toString()));
                }
            });
            map.put("email", String.join(",", emailes));
            map.put("assignee", String.join(",", userNames));
            return map;
        }
        return map;
    }
}
